#ifndef COLUMN_H
#define COLUMN_H

#include <cassert>
#include "SharedArray.h"
#include "types.h"

// Array with data of any type and shared ownership (with
// counting references).
// It's element access function take type in template parameter.
// Only shallow copies by default.
class Column {
	public:
		// Uninitialized state.
		Column():
				_type(INT),
				_data(0),
				_size(0),
				_references(new int(1)) {}

		Column(Type t, int size):
				_type(t),
				_size(size),
				_references(new int(1)) {
			if (size > 0) {
				switch (_type) {
					case INT:
						_data = reinterpret_cast<void*>(new int32_t[size]);
						break;

					case DOUBLE:
						_data = reinterpret_cast<void*>(new double[size]);
						break;

					case BOOL:
						_data = reinterpret_cast<void*>(new bool[size]);
						break;

					default:
						assert(false);
				}
			} else {
				_data = 0;
			}
		}

		template <typename T>
		Column(const SharedArray<T>& array):
				_type(::type<T>()),
				_data(reinterpret_cast<void*>(array._data)),
				_size(array._size),
				_references(array._references) {
			increaseReferences();
		}

		Column(const Column& rhs):
				_type(rhs._type),
				_data(rhs._data),
				_size(rhs._size),
				_references(rhs._references) {
			increaseReferences();
		}

		~Column() {
			decreaseReferences();
		}

		Column& operator=(const Column& rhs) {
			assert(_references);
			if (_references == rhs._references)
				return *this;

			decreaseReferences();

			_type = rhs._type;
			_data = rhs._data;
			_size = rhs._size;
			_references = rhs._references;
			increaseReferences();

			return *this;
		}

		template <typename T>
		SharedArray<T> toSharedArray() {
			assert(::type<T>() == _type);
			return SharedArray<T>(reinterpret_cast<T*>(_data), _size, _references);
		}

		Column deepCopy() const {
			Column c;
			c._type = _type;
			c._data = allocArray(_type, _size);
			memcpy(c._data, _data, typeSizeOf(_type) * _size);
			c._size = _size;
			assert(*c._references == 1);

			return c;
		}

		void swap(Column& rhs) {
			Type type = _type;
			void* data = _data;
			int size = _size;
			int* references = _references;

			_type = rhs._type;
			_data = rhs._data;
			_size = rhs._size;
			_references = rhs._references;

			rhs._type = type;
			rhs._data = data;
			rhs._size = size;
			rhs._references = references;
		}

		template <typename T>
		T& at(int i) {
			assert(::type<T>() == _type);
			assert(i >= 0);
			assert(i < _size);
			return reinterpret_cast<T*>(_data)[i];
		}

		template <typename T>
		const T& at(int i) const {
			assert(::type<T>() == _type);
			assert(i >= 0);
			assert(i < _size);
			return reinterpret_cast<const T*>(_data)[i];
		}

		bool operator==(const Column& rhs) const {
			return _references == rhs._references;
		}

		bool operator!=(const Column& rhs) const {
			return !(*this == rhs);
		}

		Type type() const {
			return _type;
		}

		int size() const {
			return _size;
		}

		int references() const {
			return *_references;
		}

		template <typename T>
		T* data() {
			assert(::type<T>() == _type);
			return reinterpret_cast<T*>(_data);
		}

		template <typename T>
		const T* data() const {
			assert(::type<T>() == _type);
			return reinterpret_cast<const T*>(_data);
		}

		// shrinks array without manipulating data
		// remaining space is lost, but still deleted in destructor
		void shrink(int size) {
			assert(size <= _size);
			_size = size;
		}

	private:
		void decreaseReferences() {
			--*_references;

			if (*_references == 0) {
				switch (_type) {
					case INT:
						delete[] reinterpret_cast<int32_t*>(_data);
						break;

					case DOUBLE:
						delete[] reinterpret_cast<double*>(_data);
						break;

					case BOOL:
						delete[] reinterpret_cast<bool*>(_data);
						break;

					default:
						assert(false);
				}

				delete _references;
			}
		}

		void increaseReferences() {
			++*_references;
		}

		Type _type;
		void* _data;
		int _size;
		int* _references;
};

#endif
